修路 
Time Limit: 20 Sec Memory Limit: 256 MB
Description 
村子间的小路年久失修,为了保障村子之间的往来,法珞决定带领大家修路。对于边带权的无向图 G = (V, E),
请选择一些边,使得1 <= i <= d, i号节点和 n - i + 1 号节点可以通过选中的边连通,最小化选中的所有边
的权值和。
第一行两个整数 n, m,表示图的点数和边数。接下来的 m行,每行三个整数 ui, vi, wi,表示有一条 ui 与 vi
之间,权值为 wi 的无向边。
Output 
仅一行一个整数表示答案。
5 5 2 
  1 3 4 
  3 5 2 
  2 3 1 
  3 4 4 
  2 4 3
Sample Output 
9
HINT 
1 <= d <= 4
2d <= n <= 10^4
0 <= m <= 10^4
1 <= ui, vi <= n
1 <= wi <= 1000
Main idea 
给定若干对点,选择若干边,询问满足每对点都连通的最小代价。
Solution 
发现 d 非常小,所以我们显然可以使用斯坦纳树来求解。
斯坦纳树是用来解决这种问题的:给定若干关键点,求使得关键点连通的最小代价 。
我们可以令 f[i][opt] 表示以 i 为根时,关键点连通态为opt的最小代价 。(以二进制表示是否连通)
然后我们就可以用两种方法来更新 f[i][opt]:
1. 设定集合x,y,x∪y=opt且x∩y=∅,那么我们显然就可以将用x,y合并来更新opt,  
  2. 若 f[j][opt] 中opt = f[i][opt]中opt,那么我们就可以以连边方式合并两个集合,这种合并方式显然可以用最短路实现,使得答案更优。 
然后我们就可以求出所有状态的f[i][opt],接下来再利用DP,求解。
定义Ans[opt]表示连通态为opt时最小代价 ,如果对应点同时连通或不连通则可以更新,枚举所有情况就可以求出答案了。
Code 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #include <bits/stdc++.h>  using  namespace  std;const  int  ONE = 20005 ;const  int  MOD = 1e9 +7 ;int  n,m,d;int  x,y,z;int  a[ONE];int  next[ONE],first[ONE],go[ONE],w[ONE],tot;int  All,f[ONE/2 ][258 ],INF;int  q[10000005 ],vis[ONE],tou,wei;int  Ans[258 ];int  get ()  {    int  res=1 ,Q=1 ;    char  c;     while ( (c=getchar ())<48  || c>57 )         if (c=='-' )Q=-1 ;     if (Q) res=c-48 ;     while ((c=getchar ())>=48  && c<=57 )         res=res*10 +c-48 ;     return  res*Q; } void  Add (int  u,int  v,int  z)  {    next[++tot]=first[u];    first[u]=tot;    go[tot]=v;    w[tot]=z;     next[++tot]=first[v];    first[v]=tot;    go[tot]=u;    w[tot]=z; } namespace  Steiner{     void  pre ()       {        memset (f,63 ,sizeof  (f));    INF=f[0 ][0 ];         int  num = 0 ;         for (int  i=1 ;i<=d;i++) f[i][1 <<num] = 0 ,    num++;         for (int  i=n-d+1 ;i<=n;i++) f[i][1 <<num] = 0 , num++;         All = (1 <<num) - 1 ;     }     void  Spfa (int  opt)       {        while (tou<wei)         {             int  u=q[++tou];             for (int  e=first[u];e;e=next[e])             {                 int  v=go[e];                 if (f[v][opt] > f[u][opt] + w[e])                 {                     f[v][opt] = f[u][opt] + w[e];                     if (!vis[v])                     {                         vis[v] = 1 ;                         q[++wei] = v;                     }                 }             }             vis[u] = 0 ;         }     }     void  Deal ()       {        for (int  opt=0 ;opt<=All;opt++)         {             for (int  i=1 ;i<=n;i++)             {                 for (int  sub=opt;sub;sub=(sub-1 ) & opt)                     f[i][opt] = min (f[i][opt],f[i][sub]+f[i][opt^sub]);                 if (f[i][opt] != INF)                 {                     q[++wei] = i;                     vis[i] = 1 ;                 }             }             Spfa (opt);         }     } } bool  Check (int  opt)  {    for (int  i=0 ,j=(d<<1 )-1 ; i<d; i++,j--)         if ( ((opt & (1 <<i))== 0 ) !=  ((opt & (1 <<j))==0 ) )             return  0 ;     return  1 ; } int  main ()  {    n=get ();    m=get ();    d=get ();     for (int  i=1 ;i<=m;i++)     {         x=get ();    y=get ();    z=get ();         Add (x,y,z);     }     Steiner::pre ();     Steiner::Deal ();     memset (Ans,63 ,sizeof  (Ans));     for (int  opt=0 ;opt<=All;opt++)         if (Check (opt))         {             for (int  i=1 ;i<=n;i++)                 Ans[opt] = min (Ans[opt], f[i][opt]);         }     for (int  opt=0 ;opt<=All;opt++)         for (int  sub=opt;sub;sub=(sub-1 ) & opt)             Ans[opt] = min (Ans[opt], Ans[sub]+Ans[opt^sub]);     if (Ans[All] == INF) printf ("-1" );     else  printf ("%d" ,Ans[All]); }